package zack.project.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import zack.project.domain.award.adapter.repository.IAwardRepository;
import zack.project.domain.award.model.aggregate.GiveOutPrizesAggregate;
import zack.project.domain.award.model.aggregate.UserAwardRecordAggregate;
import zack.project.domain.award.model.entity.TaskEntity;
import zack.project.domain.award.model.entity.UserAwardRecordEntity;
import zack.project.domain.award.model.entity.UserCreditAwardEntity;
import zack.project.domain.award.model.valobj.AccountStatusVO;
import zack.project.infrastructure.event.EventPublisher;
import zack.project.infrastructure.persistent.dao.*;
import zack.project.infrastructure.persistent.po.*;
import zack.project.infrastructure.persistent.redis.IRedisService;
import zack.project.types.common.Constants;
import zack.project.types.enums.ResponseCode;
import zack.project.types.exception.AppException;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author A1793
 */
@Repository
@Slf4j
public class AwardRepository implements IAwardRepository {
    @Resource
    private IUserAwardRecordDao userAwardRecordDao;
    @Resource
    private ITaskDao taskDao;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private EventPublisher eventPublisher;
    @Resource
    private IUserRaffleOrderDao userRaffleOrderDao;
    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IAwardDao awardDao;
    @Resource
    private IRedisService redisService;

    /**
     * 保存中奖记录(更新数据库表{user_raffle_award})和发奖的任务(该任务是为了防止本方法中发送消息失败的补偿任务，更新数据库表{task})，
     * 更新抽奖单状态为used(更新库表{user_raffle_order}) 并 向SendAwardCustomer监听的消息队列投入发奖消息
     * @param userAwardRecordAggregate
     */
    @Override
    public void saveUserAwardRecord(UserAwardRecordAggregate userAwardRecordAggregate) {
        //获得用户的奖品信息
        UserAwardRecordEntity userAwardRecordEntity = userAwardRecordAggregate.getUserAwardRecordEntity();
        //获得任务实体
        TaskEntity taskEntity = userAwardRecordAggregate.getTaskEntity();
        String userId = userAwardRecordEntity.getUserId();
        Long activityId = userAwardRecordEntity.getActivityId();
        Integer awardId = userAwardRecordEntity.getAwardId();

        //根据奖品信息构建中奖记录po对象
        UserAwardRecord userAwardRecord = new UserAwardRecord();
        userAwardRecord.setUserId(userAwardRecordEntity.getUserId());
        userAwardRecord.setActivityId(userAwardRecordEntity.getActivityId());
        userAwardRecord.setStrategyId(userAwardRecordEntity.getStrategyId());
        userAwardRecord.setOrderId(userAwardRecordEntity.getOrderId());
        userAwardRecord.setAwardId(userAwardRecordEntity.getAwardId());
        userAwardRecord.setAwardTitle(userAwardRecordEntity.getAwardTitle());
        userAwardRecord.setAwardTime(userAwardRecordEntity.getAwardTime());
        userAwardRecord.setAwardState(userAwardRecordEntity.getAwardState().getCode());

        Task task = new Task();
        task.setUserId(taskEntity.getUserId());
        task.setTopic(taskEntity.getTopic());
        task.setMessageId(taskEntity.getMessageId());
        task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
        task.setState(taskEntity.getState().getCode());
        //本次事务操作中，会完成中奖记录入库相关行为，所以需要将奖品对应的抽奖单状态设置为used，
        // 故先构建查询对象用来查询数据库表{user_raffle_order}
        UserRaffleOrder userRaffleOrderReq = new UserRaffleOrder();
        userRaffleOrderReq.setUserId(userId);
        userRaffleOrderReq.setOrderId(userAwardRecordEntity.getOrderId());


        try {
            //接下来的操作走同一个手动路由，因为一个事务操作要在同一个数据库下，
            // 操作结果该用户的本次中奖信息的补偿消息和中奖记录会在同一个数据库下
            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    // 写入记录
                    userAwardRecordDao.insert(userAwardRecord);
                    // 写入任务
                    taskDao.insert(task);
                    //更新抽奖单状态
                    int updated = userRaffleOrderDao.updateUserRaffleOrderStateUsed(userRaffleOrderReq);
                    if (1 != updated) {
                        status.setRollbackOnly();
                        log.error("写入中奖记录，用户抽奖单已使用过，不可重复抽奖 userId: {} activityId: {} awardId: {}", userId, activityId, awardId);
                        throw new AppException(ResponseCode.ACTIVITY_ORDER_ERROR.getCode(), ResponseCode.ACTIVITY_ORDER_ERROR.getInfo());
                    }
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入中奖记录，唯一索引冲突 userId: {} activityId: {} awardId: {}", userId, activityId, awardId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
        }

        //当事务写入一条task记录同时发出一条消息
        try {
            //topic: send_award , message: EventMessage.<SendAwardMessage>
            //向消息队列中放入一个发奖消息,由trigger层下的SendAwardCustomer监听器监听并消费，
            // 开始执行发奖，更新{user_award_record}表中的UserAwardRecord的award_state状态为completed，
            // 并更新用户的积分账户{user_credit_account}
            eventPublisher.publish(taskEntity.getTopic(), task.getMessage());
            taskDao.updateTaskSendMessageCompleted(task);
            log.info("写入中奖记录，发送MQ消息完成 userId: {} orderId:{} topic: {}", userId, userAwardRecordEntity.getOrderId(), task.getTopic());

        } catch (Exception e) {
            log.error("写入中奖记录，发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
            taskDao.updateTaskSendMessageFail(task);
        }
    }

    /**
     * 更新用户积分账户(修改库表{user_credit_account})和中奖记录(修改库表{user_award_record})
     * @param giveOutPrizesAggregate
     */
    @Override
    public void saveGiveOutPrizesAggregate(GiveOutPrizesAggregate giveOutPrizesAggregate) {
        //获取中奖记录
        UserAwardRecordEntity userAwardRecordEntity = giveOutPrizesAggregate.getUserAwardRecord();
        //获取积分奖品实体
        UserCreditAwardEntity userCreditAwardEntity = giveOutPrizesAggregate.getUserCreditAwardEntity();
        String userId = giveOutPrizesAggregate.getUserId();

        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(userId);
        userCreditAccountReq.setTotalAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAvailableAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode());

        UserCredit userAwardRecord = new UserCredit();
        userAwardRecord.setUserId(userId);
        userAwardRecord.setAwardState(userAwardRecordEntity.getAwardState().getCode());
        userAwardRecord.setOrderId(userAwardRecordEntity.getOrderId());
        //在修改用户积分和中奖单状态前先加锁
        //防止发奖消息被多个应用同时监听而重复消费，这里加分布式锁，谁抢到谁消费
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK + userId);
        dbRouter.doRouter(userId);
        try {
            lock.lock(3, TimeUnit.SECONDS);
            transactionTemplate.execute(status -> {
                try {
                    //查询用户的积分账户，如果没有则插入当前的积分账户实体，存在则在原来的基础上update
                     UserCreditAccount userCreditAccount  = userCreditAccountDao.
                             queryUserCreditAccount(userCreditAccountReq);
                     if(userCreditAccount == null){
                         userCreditAccountDao.insert(userCreditAccountReq);

                     }else{
                         userCreditAccountDao.updateAddAmount(userCreditAccountReq);
                     }
                     //更新完用户积分账户之后再修改中奖记录的状态为completed
                    int updateRecordCount = userAwardRecordDao.updateAwardRecordCompletedState(userAwardRecord);
                    if (1 != updateRecordCount) {
                        log.warn("更新中奖记录，重复更新拦截 userId:{} giveOutPrizesAggregate:{}", userId, JSON.toJSONString(giveOutPrizesAggregate));
                        status.setRollbackOnly();
                    }
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
            lock.unlock();
        }


    }

    /**
     * 根据奖品id查询奖品的key
     * id   award_key               award_config    award_desc
     * 1	101	user_credit_random	1,100	        用户积分【优先透彻规则范围，如果没有则走配置】	2023-12-09 11:07:06	2023-12-09 11:21:31
     * 2	102	openai_use_count	5	OpenAI      增加使用次数	                            2023-12-09 11:07:06	2023-12-09 11:12:59
     * 3	103	openai_use_count	10	OpenAI      增加使用次数	                            2023-12-09 11:07:06	2023-12-09 11:12:59
     * 4	104	openai_use_count	20	OpenAI      增加使用次数	                            2023-12-09 11:07:06	2023-12-09 11:12:58
     * @param awardId
     * @return
     */
    @Override
    public String queryAwardKey(Integer awardId) {
        return awardDao.queryAwardKeyByAwardId(awardId);
    }

    @Override
    public String queryAwardConfig(Integer awardId) {
        return awardDao.queryAwardConfigByAwardId(awardId);
    }
}
